package po.entity;

import po.enums.Operator;
import po.enums.RuleType;
import po.tree.Node;
import po.tree.ResultNode;
import po.tree.Tree;
import po.utils.ParamValueHandler;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by guowei on 2019/6/9.
 */
public class Rule implements Operatable {
    private String ID;
    private String name;
    private RuleType type;
    private List<Operatable> subRules;
    private List<Operator> operators;
    private List<Param> params;
    private Node self;

    public Rule(String ID, String name) {
        this.ID = ID;
        this.name = name;
        this.type = RuleType.RULE;
        this.subRules = new ArrayList<>();
        this.operators = new ArrayList<>();
        this.params = new ArrayList<>();
    }

    @Override
    public void setNode(Node node) {
        this.self = node;
    }

    @Override
    public RuleType getType() {
        return this.type;
    }

    // the param operator will not be used, can be any value, even `null`
    // the operate method means compact param-ResultNode with self,
    // for the Rule situation, the param-ResultNode will be empty,
    // and after this func, the ResultNode should be stored in Tree.map to represent a node result
    @Override
    public void operate(ResultNode rn, Operator nil) throws Exception {
        if(rn == null) {
            throw new NullPointerException("current calculating ResultNode is null");
        }
        int subRulesSize = this.getSubRules().size();
        for(int i=0; i<subRulesSize; i++) {
            Operatable operatable = this.getSubRules().get(i);
            if(i == 0) {
                // for the first one, the calculation can directly be the `set`
                switch (operatable.getType()) {
                    case RULE:
                        Rule firstRule = (Rule)operatable;
                        ResultNode treeItem = Tree.getResultNodeMap().get(firstRule.getNode().getPosition());
                        rn.dump(treeItem);
                        break;
                    case INDICATOR:
                        operatable.operate(rn, nil);
                        break;
                }
            } else {
                // for others but first, the operator is maintained in Operator list
                Operator operator = this.operators.get(i-1);
                switch (operatable.getType()) {
                    case RULE:
                        Rule firstRule = (Rule)operatable;
                        ResultNode treeItem = Tree.getResultNodeMap().get(firstRule.getNode().getPosition());
                        rn.calculate(treeItem, operator);
                        break;
                    case INDICATOR:
                        operatable.operate(rn, operator);
                        break;
                }
            }
        }
        // after calculate all Operatable list, the params should just be handled as `AND`
        Map<String, Param> rnParams = rn.getParams();
        for(Param p : this.params) {
            if(rnParams.containsKey(p.getID())) {
                Param rnParam = rnParams.get(p.getID());
                ParamValue newParamValue = ParamValueHandler.checkParamValueCompatible(
                        rnParam.getValue(), p.getValue(), Operator.AND);
                rnParam.setValue(newParamValue);
                rn.putParam(rnParam);
            } else {
                rn.putParam(p);
            }
        }
    }

    @Override
    public Node getNode() {
        return this.self;
    }

    public void addSubRule(Operatable operatable) {
        this.subRules.add(operatable);
    }

    public void addOperator(Operator operator) {
        this.operators.add(operator);
    }

    public void addParam(Param param) {
        this.params.add(param);
    }

    public String getID() {
        return ID;
    }

    public String getName() {
        return name;
    }

    public List<Operatable> getSubRules() {
        return subRules;
    }

    public List<Operator> getOperators() {
        return operators;
    }

    public List<Param> getParams() {
        return params;
    }
}
